import React from 'react';
import PropTypes from 'prop-types';
import {
  View,
  StyleSheet,
  PanResponder,
  Animated,
  TouchableWithoutFeedback,
  Dimensions,
  Easing,
  BackHandler,
  Platform,
  Modal,
  Keyboard,
} from 'react-native';

const { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get('window');
const styles = StyleSheet.create({
  wrapper: {
    backgroundColor: 'white',
  },
  transparent: {
    zIndex: 2,
    backgroundColor: 'rgba(0,0,0,0)',
  },
  absolute: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
  content: { flex: 1 },
});

export default class ModalBox extends React.PureComponent {
  static propTypes = {
    isOpen: PropTypes.bool,
    isDisabled: PropTypes.bool,
    startOpen: PropTypes.bool,
    backdropPressToClose: PropTypes.bool,
    swipeToClose: PropTypes.bool,
    swipeThreshold: PropTypes.number,
    swipeArea: PropTypes.number,
    position: PropTypes.string,
    entry: PropTypes.string,
    backdrop: PropTypes.bool,
    backdropOpacity: PropTypes.number,
    backdropColor: PropTypes.string,
    backdropContent: PropTypes.element,
    animationDuration: PropTypes.number,
    backButtonClose: PropTypes.bool,
    easing: PropTypes.func,
    coverScreen: PropTypes.bool,
    keyboardTopOffset: PropTypes.number,
    onClosed: PropTypes.func,
    onOpened: PropTypes.func,
    onClosingState: PropTypes.func,
  };

  static defaultProps = {
    startOpen: false,
    backdropPressToClose: true,
    swipeToClose: true,
    swipeThreshold: 50,
    position: 'center',
    backdrop: true,
    backdropOpacity: 0.5,
    backdropColor: 'black',
    backdropContent: null,
    animationDuration: 400,
    backButtonClose: false,
    easing: Easing.elastic(0.8),
    coverScreen: false,
    keyboardTopOffset: Platform.OS === 'ios' ? 22 : 0,
    useNativeDriver: true,
  };

  constructor(props) {
    super(props);

    this.onBackPress = this.onBackPress.bind(this);
    this.handleOpenning = this.handleOpenning.bind(this);
    this.onKeyboardHide = this.onKeyboardHide.bind(this);
    this.onKeyboardChange = this.onKeyboardChange.bind(this);
    this.animateBackdropOpen = this.animateBackdropOpen.bind(this);
    this.animateBackdropClose = this.animateBackdropClose.bind(this);
    this.stopAnimateOpen = this.stopAnimateOpen.bind(this);
    this.animateOpen = this.animateOpen.bind(this);
    this.stopAnimateClose = this.stopAnimateClose.bind(this);
    this.animateClose = this.animateClose.bind(this);
    this.calculateModalPosition = this.calculateModalPosition.bind(this);
    this.createPanResponder = this.createPanResponder.bind(this);
    this.onViewLayout = this.onViewLayout.bind(this);
    this.onContainerLayout = this.onContainerLayout.bind(this);
    this.renderBackdrop = this.renderBackdrop.bind(this);
    this.renderContent = this.renderContent.bind(this);
    this.open = this.open.bind(this);
    this.close = this.close.bind(this);

    const position = props.startOpen
      ? new Animated.Value(0)
      : new Animated.Value(props.entry === 'top' ? -SCREEN_HEIGHT : SCREEN_HEIGHT);
    this.state = {
      position,
      backdropOpacity: new Animated.Value(0),
      isOpen: props.startOpen,
      isAnimateClose: false,
      isAnimateOpen: false,
      swipeToClose: false,
      height: SCREEN_HEIGHT,
      width: SCREEN_WIDTH,
      containerHeight: SCREEN_HEIGHT,
      containerWidth: SCREEN_WIDTH,
      isInitialized: false,
      keyboardOffset: 0,
      pan: this.createPanResponder(position),
    };

    // Needed for iOS because the keyboard covers the screen
    if (Platform.OS === 'ios') {
      this.subscriptions = [
        Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange),
        Keyboard.addListener('keyboardDidHide', this.onKeyboardHide),
      ];
    }
  }

  componentDidMount() {
    this.handleOpenning();
  }

  componentDidUpdate(prevProps) {
    if (this.props.isOpen !== prevProps.isOpen) {
      this.handleOpenning();
    }
  }

  componentWillUnmount() {
    if (this.subscriptions) this.subscriptions.forEach(sub => sub.remove());
    if (this.backSubscription) {
      this.backSubscription.remove();
      this.backSubscription = null;
    }
    this.clearAnimateOpenTask();
  }

  onBackPress() {
    this.close();
    return true;
  }

  handleOpenning() {
    if (typeof this.props.isOpen == 'undefined') return;
    if (this.props.isOpen) this.open();
    else this.close();
  }

  /****************** ANIMATIONS **********************/

  /*
   * The keyboard is hidden (IOS only)
   */
  onKeyboardHide() {
    this.setState({ keyboardOffset: 0 });
  }

  /*
   * The keyboard frame changed, used to detect when the keyboard open, faster than keyboardDidShow (IOS only)
   */
  onKeyboardChange(evt) {
    if (!evt) return;
    if (!this.state.isOpen) return;
    const keyboardFrame = evt.endCoordinates;
    const keyboardHeight = this.state.containerHeight - keyboardFrame.screenY;

    this.setState({ keyboardOffset: keyboardHeight }, () => {
      this.animateOpen();
    });
  }

  /*
   * Open animation for the backdrop, will fade in
   */
  animateBackdropOpen() {
    if (this.state.isAnimateBackdrop && this.state.animBackdrop) {
      this.state.animBackdrop.stop();
    }
    this.setState({ isAnimateBackdrop: true });

    const animBackdrop = Animated.timing(this.state.backdropOpacity, {
      toValue: 1,
      duration: this.props.animationDuration,
      easing: this.props.easing,
      useNativeDriver: this.props.useNativeDriver,
    });
    animBackdrop.start(() => {
      this.setState({
        isAnimateBackdrop: false,
        animBackdrop,
      });
    });
  }

  /*
   * Close animation for the backdrop, will fade out
   */
  animateBackdropClose() {
    if (this.state.isAnimateBackdrop && this.state.animBackdrop) {
      this.state.animBackdrop.stop();
    }
    this.setState({ isAnimateBackdrop: true });

    const animBackdrop = Animated.timing(this.state.backdropOpacity, {
      toValue: 0,
      duration: this.props.animationDuration,
      easing: this.props.easing,
      useNativeDriver: this.props.useNativeDriver,
    });
    animBackdrop.start(() => {
      this.setState({
        isAnimateBackdrop: false,
        animBackdrop,
      });
    });
  }

  /*
   * Stop opening animation
   */
  stopAnimateOpen() {
    this._positionDest = -1;
    if (this.state.isAnimateOpen) {
      if (this.state.animOpen) this.state.animOpen.stop();
      this.setState({ isAnimateOpen: false });
    }
    this.clearAnimateOpenTask();
  }

  /*
   * Open animation for the modal, will move up
   */
  animateOpen() {
    this.clearAnimateOpenTask();
    this._animateOpenTask = setTimeout(() => {
      this._animateOpenTask = null;
      this._animateOpen();
    }, 100);
  }

  clearAnimateOpenTask() {
    if (this._animateOpenTask) {
      clearTimeout(this._animateOpenTask);
      this._animateOpenTask = null;
    }
  }

  _animateOpen() {
    console.log(
      `modalBox 打开动画 isOpen:${this.state.isOpen} isAnimateOpen:${this.state.isAnimateOpen} isAnimateClose:${this.state.isAnimateClose}`
    );
    if (!(this.state.isOpen || this.state.isAnimateOpen)) {
      return;
    }
    this.stopAnimateClose();

    // Backdrop fadeIn
    if (this.props.backdrop) this.animateBackdropOpen();

    this.setState(
      {
        isAnimateOpen: true,
        isOpen: true,
      },
      () => {
        requestAnimationFrame(() => {
          // Detecting modal position
          let positionDest = this.calculateModalPosition(
            this.state.containerHeight - this.state.keyboardOffset
          );
          if (this.state.keyboardOffset && positionDest < this.props.keyboardTopOffset) {
            positionDest = this.props.keyboardTopOffset;
          }
          if (positionDest === this._positionDest) {
            console.warn('modalBox 打开动画 重复的动画');
            return;
          }
          this._positionDest = positionDest;
          if (this._animOpen) {
            this._requestAnimOpenExec = true;
          } else {
            this._requestAnimOpenExec = false;
            this.animOpenExec(positionDest);
          }
        });
      }
    );
  }

  animOpenExec(positionDest) {
    if (!(this.state.isOpen || this.state.isAnimateOpen)) {
      console.log(
        `modalBox 不需要打开动画 isOpen:${this.state.isOpen} isAnimateOpen:${this.state.isAnimateOpen} isAnimateClose:${this.state.isAnimateClose}`
      );
      return;
    }
    const animOpen = Animated.timing(this.state.position, {
      toValue: positionDest,
      duration: this.props.animationDuration,
      easing: this.props.easing,
      useNativeDriver: this.props.useNativeDriver,
    });
    this._animOpen = animOpen;
    animOpen.start(result => {
      this.animOpenCallback(positionDest, animOpen, result);
    });
  }

  animOpenCallback(positionDest, animOpen, result) {
    console.log(
      'modalBox 打开动画 完成',
      this._positionDest !== positionDest,
      this._requestAnimOpenExec,
      result
    );
    if (
      this._positionDest !== positionDest &&
      this._positionDest &&
      this._positionDest !== -1 &&
      this._requestAnimOpenExec
    ) {
      console.warn('modalBox 打开动画 完成 再次执行', this._positionDest, positionDest);
      this.animOpenExec(this._positionDest);
      return;
    }
    this._animOpen = null;
    this.setState({
      isAnimateOpen: false,
      animOpen,
      positionDest,
    });
    if (this.props.onOpened) this.props.onOpened();
  }

  /*
   * Stop closing animation
   */
  stopAnimateClose() {
    if (this.state.isAnimateClose) {
      if (this.state.animClose) this.state.animClose.stop();
      this.setState({ isAnimateClose: false });
    }
  }

  /*
   * Close animation for the modal, will move down
   */
  animateClose() {
    console.log('modalBox 关闭弹窗');
    this.stopAnimateOpen();

    // Backdrop fadeout
    if (this.props.backdrop) this.animateBackdropClose();

    this.setState(
      {
        isAnimateClose: true,
        isOpen: false,
      },
      () => {
        const animClose = Animated.timing(this.state.position, {
          toValue:
            this.props.entry === 'top' ? -this.state.containerHeight : this.state.containerHeight,
          duration: this.props.animationDuration,
          easing: this.props.easing,
          useNativeDriver: this.props.useNativeDriver,
        });
        animClose.start(() => {
          // Keyboard.dismiss();   // make this optional. Easily user defined in .onClosed() callback
          this.setState(
            {
              isAnimateClose: false,
              animClose,
            },
            () => {
              /* Set the state to the starting position of the modal, preventing from animating where the swipe stopped */
              this.state.position.setValue(
                this.props.entry === 'top'
                  ? -this.state.containerHeight
                  : this.state.containerHeight
              );
            }
          );
          if (this.props.onClosed) this.props.onClosed();
        });
      }
    );
  }

  /*
   * Calculate when should be placed the modal
   */
  calculateModalPosition(containerHeight) {
    let position = 0;

    if (this.props.position === 'bottom') {
      position = containerHeight - this.state.height;
    } else if (this.props.position === 'center') {
      position = containerHeight / 2 - this.state.height / 2;
    }
    console.log('modalBox 计算显示位置', position, containerHeight, this.state.height);
    // Checking if the position >= 0
    if (position < 0) position = 0;
    return position;
  }

  /*
   * Create the pan responder to detect gesture
   * Only used if swipeToClose is enabled
   */
  createPanResponder(position) {
    let closingState = false;
    let inSwipeArea = false;

    const onPanStart = evt => {
      if (
        !this.props.swipeToClose ||
        this.props.isDisabled ||
        (this.props.swipeArea &&
          evt.nativeEvent.pageY - this.state.positionDest > this.props.swipeArea)
      ) {
        inSwipeArea = false;
        return false;
      }
      inSwipeArea = true;
      return true;
    };

    const animEvt = Animated.event([null, { customY: position }], {
      useNativeDriver: this.props.useNativeDriver,
    });

    const onPanMove = (evt, state) => {
      const newClosingState =
        this.props.entry === 'top'
          ? -state.dy > this.props.swipeThreshold
          : state.dy > this.props.swipeThreshold;
      if (this.props.entry === 'top' ? state.dy > 0 : state.dy < 0) return;
      if (newClosingState !== closingState && this.props.onClosingState)
        this.props.onClosingState(newClosingState);
      closingState = newClosingState;
      state.customY = state.dy + this.state.positionDest;
      if (typeof animEvt === 'function') {
        animEvt(evt, state);
      }
    };

    const onPanRelease = (evt, state) => {
      if (!inSwipeArea) return;
      inSwipeArea = false;
      if (
        this.props.entry === 'top'
          ? -state.dy > this.props.swipeThreshold
          : state.dy > this.props.swipeThreshold
      ) {
        this.close();
      } else if (!this.state.isOpen) {
        this.animateOpen();
      }
    };

    return PanResponder.create({
      onStartShouldSetPanResponder: onPanStart,
      onPanResponderMove: onPanMove,
      onPanResponderRelease: onPanRelease,
      onPanResponderTerminate: onPanRelease,
    });
  }

  /*
   * Event called when the modal view layout is calculated
   */
  onViewLayout(evt) {
    const { height, width } = evt.nativeEvent.layout;
    console.log('modalBox 内容高度', height);

    let newState = {};
    if (height !== this.state.height) newState.height = height;
    if (width !== this.state.width) newState.width = width;

    this.setState(newState, () => {
      if (this.onViewLayoutCalculated) {
        this.onViewLayoutCalculated();
      } else if (this.state.isOpen || this.state.isAnimateOpen) {
        this.animateOpen();
      }
    });
  }

  /*
   * Event called when the container view layout is calculated
   */
  onContainerLayout(evt) {
    const height = evt.nativeEvent.layout.height;
    const width = evt.nativeEvent.layout.width;
    console.log('modalBox 容器高度', height);
    this.props.onContainerLayout && this.props.onContainerLayout(evt);

    // If the container size is still the same we're done
    if (height === this.state.containerHeight && width === this.state.containerWidth) {
      this.setState({ isInitialized: true });
      return;
    }

    if (this.state.isOpen || this.state.isAnimateOpen) {
      this.animateOpen();
    }

    if (this.props.onLayout) this.props.onLayout(evt);
    this.setState({
      isInitialized: true,
      containerHeight: height,
      containerWidth: width,
    });
  }

  /*
   * Render the backdrop element
   */
  renderBackdrop() {
    let backdrop = null;

    if (this.props.backdrop) {
      backdrop = (
        <TouchableWithoutFeedback onPress={this.props.backdropPressToClose ? this.close : null}>
          <View importantForAccessibility="no" style={styles.absolute}>
            <View
              style={[
                styles.absolute,
                {
                  backgroundColor: this.props.backdropColor,
                  opacity: this.props.backdropOpacity,
                },
              ]}
            />
            {this.props.backdropContent || []}
          </View>
        </TouchableWithoutFeedback>
      );
    }

    return backdrop;
  }

  renderContent() {
    const size = {
      height: this.state.containerHeight,
      width: this.state.containerWidth,
    };
    const offsetX = (this.state.containerWidth - this.state.width) / 2;

    return (
      <Animated.View
        onLayout={this.onViewLayout}
        style={[
          styles.wrapper,
          size,
          this.props.style,
          {
            transform: [{ translateY: this.state.position }, { translateX: offsetX }],
          },
        ]}
        {...this.state.pan.panHandlers}
      >
        {this.props.children}
      </Animated.View>
    );
  }

  /*
   * Render the component
   */
  render() {
    const visible = this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose;

    if (!visible) return <View />;

    const content = (
      <View
        importantForAccessibility="yes"
        accessibilityViewIsModal={true}
        style={[styles.transparent, styles.absolute]}
        pointerEvents="box-none"
      >
        <View style={styles.content} pointerEvents="box-none" onLayout={this.onContainerLayout}>
          {visible && this.renderBackdrop()}
          {visible && this.renderContent()}
        </View>
      </View>
    );

    if (!this.props.coverScreen) return content;

    return (
      <Modal
        onRequestClose={() => {
          if (this.props.backButtonClose) {
            this.close();
          }
        }}
        supportedOrientations={['landscape', 'portrait', 'portrait-upside-down']}
        transparent
        visible={visible}
        hardwareAccelerated={true}
      >
        {content}
      </Modal>
    );
  }

  /****************** PUBLIC METHODS **********************/

  open() {
    if (this.props.isDisabled) return;
    if (!this.state.isAnimateOpen && (!this.state.isOpen || this.state.isAnimateClose)) {
      // 👇先标记为等待 open
      this.onViewLayoutCalculated = () => {
        requestAnimationFrame(() => {
          this.animateOpen();
          if (this.props.backButtonClose && Platform.OS === 'android' && !this.backSubscription) {
            this.backSubscription = BackHandler.addEventListener(
              'hardwareBackPress',
              this.onBackPress
            );
          }
          this.onViewLayoutCalculated = null;
        });
      };
      this.setState({ isAnimateOpen: true });
    }
  }

  close() {
    if (this.props.isDisabled) return;
    if (!this.state.isAnimateClose && (this.state.isOpen || this.state.isAnimateOpen)) {
      this.animateClose();
      if (this.backSubscription) {
        this.backSubscription.remove();
        this.backSubscription = null;
      }
    }
  }
}
